#include "stdafx.h"
#include <windows.h>

#include "CMFT.h"
#include "Attrs.h"
#include "scoped_array.h"
#include "MFT.h"
#include "volumereader.h"
#include "CAttribute.h"

#include <QtGlobal>
 

CMFT::~CMFT(void)
{
}

QString CMFT::getFileName() const
{
	//find the fileName with smallest namespace;
	int curNS = FileNameNS::Invalid;
	int index = -1;
	for (int i = 0;i<resAttrs.size();i++)
		if (resAttrs[i]->Header.TypeID==AttrType::FileName) {
			ResidentAttrHdr* newAtt = resAttrs[i];
			FileNameAttr * fn = (FileNameAttr*)((char*)newAtt+newAtt->ContentOffset);
			if (fn->Namespace < curNS) {
				curNS = fn->Namespace;
				index = i;
			}
		}
		if (curNS==FileNameNS::Invalid) //< no filename found
			return QString::null;
		else {
			ResidentAttrHdr* newAtt = resAttrs[index];
			FileNameAttr * fn = (FileNameAttr*)((char*)newAtt+newAtt->ContentOffset);
			QString ret = wArrayToQString((WCHAR*)((char*)fn+sizeof(FileNameAttr)),fn->NameLen);
			return ret;
		}
}

//************************************
// Method:    getAttPos
// FullName:  CMFT::getAttPos
// Access:    public 
// Returns:   UINT64
//	the offset on disk
// Qualifier:
// Parameter: int attrType
// Parameter: int attrId
// Parameter: UINT64 startByte
//************************************
UINT64 CMFT::getAttPos( int attrType, int attrId, UINT64 startByte )
{
	NonResidentAttrHdr * att = NULL;
	int index = -1;
	for (int i = 0;i<nresAttrs.size();i++)
		if (nresAttrs[i]->Header.TypeID && (nresAttrs[i]->Header.ID == attrId || attrId < 0)) {
			att = nresAttrs[i];
			index = i;
			break;
		}
		Q_ASSERT_X(index!=-1,"getAttPos","must have this attType and id ,and attribute must be non-resident");
		UINT64 ret = runLists[index].getPosOnDisk(startByte);
		return ret;
}

int CMFT::countAttr( int attrType )
{
	int ret=0;
	for (int i = 0;i<nresAttrs.size();i++)
		if (nresAttrs[i]->Header.TypeID==attrType)
			ret++;
	for (int i = 0;i<resAttrs.size();i++)
		if (resAttrs[i]->Header.TypeID==attrType)
			ret++;
	return ret;
}

//TODO: neater method to implement 
bool CMFT::hasExisted()
{
	MFT * mft = (MFT*)mftData[0].get();
	if (mft->NextAttrID==0 ) 
		return false;
	else return true;
}

bool CMFT::isBase()
{
	MFT * mft = (MFT*)mftData[0].get();
	UINT64 BaseRef = mft->BaseRecordRef;
	UINT16 BaseSeq = BaseRef>>48;//<high part of |BaseRef|
	BaseRef = BaseRef ^ ((UINT64)BaseSeq<<48);//<erase high part, now |BaseRef| is mft number
	return BaseRef==0;	
}

QList<Range> getClusterRange() 
{
	throw std::exception("The method or operation is not implemented.");
}


CAttribute CMFT::getAttribute( int attrType,int id)
{
	//find the attribute
	bool isResident = true;
	int index = -1;
	for (int i = 0;i<resAttrs.size();i++)
		if (resAttrs[i]->Header.TypeID==attrType && resAttrs[i]->Header.ID==id)
			index = i;
	if (index==-1) {
		isResident = false;
		for (int i = 0;i<nresAttrs.size();i++)
			if (nresAttrs[i]->Header.TypeID==attrType && nresAttrs[i]->Header.ID==id)
				index = i;
	}
	Q_ASSERT_X(index!=-1,"getAttribute"," the attribute must be found");
	CAttribute ret(this);
	//construct retval
	ret.isResident = isResident;
	if (isResident)
		ret.resAttr = resAttrs[index];
	else {
		ret.nresAttr = nresAttrs[index];
		ret.clusterRange = runLists[index].getClusterRange();
	}
	ret.attrDat = mftData;

	return ret;
}

CAttribute CMFT::getAttribute( int attrType )
{
	//HACK: if have multiple same attributes, get the first one
	Q_ASSERT_X(countAttr(attrType)>=1,"getAttribute","have exactly 1 or more such attribute");

	bool isResident = true;
	int index = -1;
	for (int i = 0;i<resAttrs.size();i++)
		if (resAttrs[i]->Header.TypeID==attrType)
			index = i;
	if (index==-1) {
		isResident = false;
		for (int i = 0;i<nresAttrs.size();i++)
			if (nresAttrs[i]->Header.TypeID==attrType)
				index = i;
	}
	Q_ASSERT_X(index!=-1,"getAttribute"," the attribute must be found");
	CAttribute ret(this);
	//construct retval
	ret.isResident = isResident;
	if (isResident)
		ret.resAttr = resAttrs[index];
	else {
		ret.nresAttr = nresAttrs[index];
		ret.clusterRange = runLists[index].getClusterRange();
	}
	ret.attrDat = mftData;

	return ret;

}

UINT64 CMFT::entryNo()
{
	UINT64 ret = parentVR->getEntryNo(diskOffSet);
	return ret;
}



NTFSFileTime CMFT::getTimes()
{
	Q_ASSERT(countAttr(AttrType::StandardInfo)==1);
	Q_ASSERT_X(countAttr(AttrType::FileName)>=1,"getTimes","should have one or more FileName Attr");
	CAttribute stdInfo = getAttribute(AttrType::StandardInfo);
	CAttribute fileName = getAttribute(AttrType::FileName);
	STDInfoAttr * pStdInfo = (STDInfoAttr*)((char*)stdInfo.resAttr+stdInfo.resAttr->ContentOffset);
	NTFSFileTime ret;
	ret.stdInfoTime.access = FileTimeToSysTime(&pStdInfo->FileATime);
	ret.stdInfoTime.modify = FileTimeToSysTime(&pStdInfo->FileMTime);
	ret.stdInfoTime.creation = FileTimeToSysTime(&pStdInfo->CTime);
	ret.stdInfoTime.entry = FileTimeToSysTime(&pStdInfo->MFTMTime);
	FileNameAttr * pFileName = (FileNameAttr*)((char*)fileName.resAttr+fileName.resAttr->ContentOffset);
	ret.fileNameTime.access = FileTimeToSysTime(&pFileName->FileATime);
	ret.fileNameTime.modify = FileTimeToSysTime(&pFileName->FileMTime);
	ret.fileNameTime.creation = FileTimeToSysTime(&pFileName->CTime);
	ret.fileNameTime.entry = FileTimeToSysTime(&pFileName->MFTMTime);

	return ret;
}

bool CMFT::isDeleted()
{
	MFT* mft = (MFT*)mftData[0].get();
	return hasExisted() && !(mft->Flags & MFT::InUse);
}

void CMFT::append( CMFT & child )
{
	//mftData.insert(mftData.end(),child.mftData.begin(),child.mftData.end());
	mftData.append(child.mftData);
	//resAttrs.insert(resAttrs.end(),child.resAttrs.begin(),child.resAttrs.end());
	resAttrs.append(child.resAttrs);
	for (int i =0;i<child.nresAttrs.size();i++) {
		NonResidentAttrHdr* nres = child.nresAttrs[i];
		if (hasAttr(nres->Header.TypeID,nres->Header.ID)) {
			combineRunList(nres->Header.TypeID,nres->Header.ID,child.runLists[i]);
		} else {
			nresAttrs.push_back(child.nresAttrs[i]);
			runLists.push_back(child.runLists[i]);
		}
	}
}

UINT64 CMFT::getBaseRef()
{
	MFT* mft = (MFT*)mftData[0].get();
	return mft->BaseRecordRef;
}

void CMFT::combineRunList( int attrType, int attrId, RunList runList )
{
	bool found = true;
	int i = 0;
	for (i = 0;i<this->nresAttrs.size();i++) {
		if (this->nresAttrs[i]->Header.TypeID==attrType && 
			nresAttrs[i]->Header.ID==attrId) {
				RunList & left = runLists[i];
				left.Combine(runList);
				return;
		}
	}
	Q_ASSERT(0);//must have combined and returned 
}

bool CMFT::hasAttr( int attrType,int attrId )
{
	for (int i = 0;i<this->resAttrs.size();i++) 
		if (this->resAttrs[i]->Header.TypeID==attrType && 
			resAttrs[i]->Header.ID==attrId) {
				return true;
		}
		for (int i = 0;i<this->nresAttrs.size();i++) 
			if (this->nresAttrs[i]->Header.TypeID==attrType && 
				nresAttrs[i]->Header.ID==attrId) {
					return true;
			}
			return false;
}

QString CMFT::getPath()
{
	_isOrphan = false;
	if (this->entryNo()==5)//root
		return "";
	QString ret = "";
	UINT64 pNo = getParentRef();
	
	while (pNo!=0) {
		UINT64 entry = pNo & (((UINT64)1<<48)-1);//lower 48 bit
		UINT16 lastSeq = pNo>>48;
		CMFT cur = parentVR->MFTEntry(pNo);
		UINT16 curSeq = cur.getFileRef()>>48;
		if (lastSeq==curSeq || 
			(cur.isDeleted() && (lastSeq+1)%256 == curSeq)) {//< is not orphan						
		} else {//< is orphan
			//quick return , retain partial path
			_isOrphan = true;
			return ret;
		}
		ret = cur.getFileName()+"\\"+ret;
		if (pNo == cur.getParentRef())
			break;
		else pNo = cur.getParentRef();
	}
	return ret;	
}

CMFT  CMFT::getParentDir() {
	UINT64 pref = this->getParentRef();
	pref &= 0xffffffffffff;
	CMFT ret = parentVR->MFTEntry(pref);
	return ret;
}

UINT64 CMFT::getParentRef()
{
	CAttribute FN = getAttribute(AttrType::FileName);
	FileNameAttr * pFN = (FileNameAttr *)((char*)FN.resAttr + FN.resAttr->ContentOffset);
	return pFN->ParentRef;
}

bool CMFT::isOrphan()
{
	return _isOrphan;
}

UINT64 CMFT::getFileRef()
{
	MFT *mft = (MFT*)mftData[0].get();
	UINT64 entry = entryNo();
	UINT64 ret = ((UINT64)mft->SeqNo<<48) | entry;
	return ret;
}

bool isCollide( UINT64 sa,UINT64 ea,UINT64 sb, UINT64 eb ) 
{
	return sb<=ea && sa<=eb;
}

QList<Range> CMFT::getCollision( CMFT * other )
{
	QList<Range> ret;
	for (QList<RunList>::iterator itMe = runLists.begin();itMe!=runLists.end();itMe++) {
		for (QList<RunList>::iterator itOther = other->runLists.begin();
			itOther!=other->runLists.end();	itOther++) {
				QList<Range> rangeMe = itMe->getClusterRange();
				QList<Range> rangeOther = itOther->getClusterRange();
				for (int i = 0;i<rangeMe.size();i++)
					for (int j = 0;j<rangeOther.size();j++)
						if (isCollide(rangeMe[i].start,rangeMe[i].end,rangeOther[j].start,rangeOther[j].end))
							ret.push_back(Range(max(rangeMe[i].start,rangeOther[j].start),
								min(rangeMe[i].end,rangeOther[j].end)));
		}
	}
	return ret;
}

bool CMFT::haveTimes()
{
	return (countAttr(AttrType::FileName)>=1 && 
		countAttr(AttrType::StandardInfo)>=1);

}

QString CMFT::getFileNameExtension()
{
	QString fn = getFileName();
	int pos = fn.lastIndexOf('.');
	if (pos==-1)
		return "";
	QString ret(fn.mid(pos+1));
	return ret;
}

bool CMFT::isDirectory()
{
	MFT* mft = (MFT*)mftData[0].get();
	if (mft->Flags & MFT::Directory) {
		return true;
	}else return false;
}

UINT64 CMFT::getLSN()
{
	MFT* mft= (MFT*)mftData[0].get();
	return mft->LSN;
}


